﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using YmtAuth.Common;
using YmtAuth.Domain.Repository;
using YmtSystem.CrossCutting;

namespace YmtAuth.Repository.DapperRepository
{
    public class _Repository<TEntity> : _IMsSqlRepository<TEntity> where TEntity : class 
    {
        private ConcurrentDictionary<Type, List<string>> paramNameCache = new ConcurrentDictionary<Type, List<string>>();
        private ConcurrentDictionary<Type, object> instanceCache = new ConcurrentDictionary<Type, object>();
        private readonly string table;
        protected string connString;
        private Func<string,bool, _DbContext> DBContextFunc;
      
        protected ParametersWrapper Parameters
        {
            get { return ParametersWrapper.Factory; }
        }

        /// <summary>
        /// 获取数据库操作上下文
        /// </summary>
        /// <param name="_connString">链接字符，不传值则是构造函数初始化链接字符</param>
        /// <returns></returns>
        protected _DbContext GetDbContext(bool siBindThreadContext = false, string _connString = null)
        {
            return DBContextFunc(_connString ?? this.connString, siBindThreadContext);
        }

        public _Repository(string connString, string defalutTableName,Func<string,bool,_DbContext> contextFunc)
        {
            this.connString = connString;
            this.table = defalutTableName;
            this.DBContextFunc = contextFunc;
        }

        public virtual TEntity First(dynamic where)
        {
            var tmpEntity = instanceCache.GetOrAdd(typeof (TEntity),
                t => System.Activator.CreateInstance(typeof (TEntity), true));
            var select = GetParamNames((object) tmpEntity);
            if (where == null)
            {
                using (var conn = GetDbContext())
                {
                    return
                        conn.Connection.Query<TEntity>("SELECT TOP 1 " + select.Select(p => p + ",") + " FROM " + table)
                            .FirstOrDefault();
                }
            }
            var paramNames = GetParamNames((object) where);
            var w = string.Join(" AND ", paramNames.Select(p => "" + p + " = @" + p));
            var selectName = string.Join(" ,", select.Select(p => p));
            var parameters2 = new DynamicParameters(where);
            parameters2.AddDynamicParams(where);
            using (var conn = GetDbContext())
            {
                return
                    conn.Connection.Query<TEntity>("SELECT top 1 " + selectName + " FROM " + table + " WHERE " + w,
                        parameters2).FirstOrDefault();
            }
        }

        public virtual IEnumerable<TEntity> Query(string sql, DynamicParameters parameters, int? timeOut = null,
            CommandType? commandType = null,
            bool buffered = true)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            using (var conn = GetDbContext())
            {
                return conn.Connection.Query<TEntity>(sql, parameters, null, buffered, timeOut, commandType);
            }
        }

        public virtual IEnumerable<TEntity> Query<T1, T2, TEntity>(string sql, DynamicParameters parameters,
            Func<T1, T2, TEntity> convert, string splitOn, int? timeOut = null, CommandType? commandType = null,
            bool buffered = true)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            using (var conn = GetDbContext())
            {
                return conn.Connection.Query<T1, T2, TEntity>(sql, convert, parameters, null, buffered, splitOn, timeOut,
                    commandType);
            }
        }

        public virtual TEntity QueryMultiple(string sql, DbParameter[] parameters,
            Func<SqlMapper.GridReader, TEntity> convert, int? timeOut = null, CommandType? commandType = null,
            bool buffered = true)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            var par = parameters.ToDapperParameters();
            using (var conn = GetDbContext())
            {
                var result = conn.Connection.QueryMultiple(sql, par, null, timeOut, commandType);
                return convert(result);
            }
        }

        public virtual long Insert(TEntity entity)
        {
            YmtSystemAssert.AssertArgumentNotNull(entity, "实体不能为空");
            var o = (object) entity;
            var paramNames = GetParamNames(o);
           
            var cols = string.Join(",", paramNames);
            var cols_params = string.Join(",", paramNames.Select(p => "@" + p));
            var sql = "INSERT INTO " + table + " (" + cols + ") VALUES (" + cols_params + "); SELECT @@IDENTITY";
            using (var conn = GetDbContext())
            {                
                var id = conn.Connection.Query(sql, o).Single() as IDictionary<string, object>;
                return Convert.ToInt64(id.Values.Single());
            }
        }

        public virtual long Insert(TEntity entity, bool ignoreDuplicate)
        {
            try
            {
                return Insert(entity);
            }
            catch (SqlException ex)
            {
                if (ignoreDuplicate && ex.Number == 1062)
                    return 0;
                throw;
            }
        }

        public virtual void InsertAsync(TEntity entity, bool ignoreDuplicate)
        {
            Task.Factory.StartNew(() =>
            {
                try
                {
                    return Insert(entity);
                }
                catch (SqlException ex)
                {
                    if (ignoreDuplicate && ex.Number == 1062)
                        return 0;
                    throw;
                }
            })
                .ContinueWith(t => YmatouLoggingService.Error("insert ms sql error {0}", t.Exception),
                    TaskContinuationOptions.OnlyOnFaulted);
        }

        public virtual async Task<int> InsertAsync(TEntity entity,IDictionary<string,object> vlues )
        {
            YmtSystemAssert.AssertArgumentNotNull(entity, "实体不能为空");
            var o = (object)entity;
            var paramNames = GetParamNames(o);          
            var cols = string.Join(",", paramNames);
            var _par = this.Parameters;
            foreach (var v in vlues)
            {
                _par.Add(string.Format("@{0}", v.Key), v.Value);
            }
           
            var cols_params = string.Join(",", paramNames.Select(p => "@" + p));
            var sql = "INSERT INTO " + table + " (" + cols + ") VALUES (" + cols_params + "); SELECT @@IDENTITY";
            using (var conn = GetDbContext())
            {
                conn.Connection.Open();
                var result =await conn.Connection.QueryAsync<object>(sql,  _par.Build()).ConfigureAwait(false);
                return Convert.ToInt32((result.Single() as IDictionary<string,object>).Values.Single());
            }
        }

        public int Insert(TEntity entity, IDictionary<string, object> vlues)
        {
            YmtSystemAssert.AssertArgumentNotNull(entity, "实体不能为空");            
            var paramNames = GetParamNames((object)entity);
            var cols = string.Join(",", paramNames);
            var _par = this.Parameters;
            foreach (var v in vlues)
            {
                _par.Add(string.Format("@{0}", v.Key), v.Value);
            }
            var cols_params = string.Join(",", paramNames.Select(p => "@" + p));
            var sql = "INSERT INTO " + table + " (" + cols + ") VALUES (" + cols_params + "); SELECT @@IDENTITY";
            using (var conn = GetDbContext())
            {
                var result = conn.Connection.Query<int>(sql, _par.Build());
                return result.Single();
            }
        }

        public virtual int Update(IDictionary<string, object> updateColumns, IDictionary<string, object> where)
        {
            YmtSystemAssert.AssertArgumentNotNull(updateColumns, "更新列不能为空");
            YmtSystemAssert.AssertArgumentNotNull(where, "更新条件不能为空");
            var b = new StringBuilder();
            b.Append("UPDATE ").Append(table).Append(" SET ");
            b.AppendLine(string.Join(",", updateColumns.Keys.Select(p => "" + p + "= @" + p)));
            b.Append(" WHERE ").Append(string.Join(" AND ", where.Keys.Select(p => "" + p + " = @" + p)));
            var parameters = where.ToDapperParameters().ToDapperParameters(updateColumns);
            using (var conn = GetDbContext())
            {
                return conn.Connection.Execute(b.ToString(), parameters);
            }
        }

        public virtual int Update(IDictionary<string, object> updateColumns, IDictionary<string, object> where,
            string updateTableName)
        {
            YmtSystemAssert.AssertArgumentNotNull(updateColumns, "更新列不能为空");
            YmtSystemAssert.AssertArgumentNotNull(where, "更新条件不能为空");
            YmtSystemAssert.AssertArgumentNotNull(updateTableName, "更新表不能为空");
            var b = new StringBuilder();
            b.Append("UPDATE ").Append(updateTableName).Append(" SET ");
            b.AppendLine(string.Join(",", updateColumns.Keys.Select(p => "" + p + "= @" + p)));
            b.Append(" WHERE ").Append(string.Join(" AND ", where.Keys.Select(p => "" + p + " = @" + p)));
            var parameters = where.ToDapperParameters().ToDapperParameters(updateColumns);
            using (var conn = GetDbContext())
            {
                return conn.Connection.Execute(b.ToString(), parameters);
            }
        }

        public virtual int Execute(string sql, IDictionary<string, object> parameter)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            var tmpparams = parameter.ToDapperParameters();
            using (var conn = GetDbContext())
            {
                return conn.Connection.Execute(sql, tmpparams);
            }
        }

        public virtual int Execute(string sql, DbParameter[] parameter, int? timeOut = null,
            CommandType? commandType = null)
        {
            YmtSystemAssert.AssertArgumentNotEmpty(sql, "SQL 不能为空");
            var tmpparams = parameter.ToDapperParameters();
            using (var conn = GetDbContext())
            {
                return conn.Connection.Execute(sql, tmpparams, commandTimeout: timeOut, commandType: commandType);
            }
        }

        public virtual bool Delete<TId>(TId id)
        {
            using (var conn = GetDbContext())
            {
                return
                    conn.Connection.Execute("DELETE FROM " + table + " WHERE Id = @id",
                        this.Parameters.Add("@id", id, DbType.Int32).Build()) > 0;
            }
        }

        public virtual bool Delete(dynamic where = null)
        {
            if (where == null)
            {
                throw new ArgumentNullException("where can't null");
            }
            var paramNames = GetParamNames((object) where);
            var w = string.Join(" AND ", paramNames.Select(p => "" + p + "= @" + p));
            using (var conn = GetDbContext())
            {
                return conn.Connection.Execute("DELETE FROM " + table + " WHERE " + w) > 0;
            }
        }

        List<string> GetParamNames(object o)
        {
            if (o is DynamicParameters)
            {
                return (o as DynamicParameters).ParameterNames.ToList();
            }

            List<string> paramNames;
            if (!paramNameCache.TryGetValue(o.GetType(), out paramNames))
            {
                paramNames = new List<string>();
                foreach (
                    var prop in
                        o.GetType()
                            .GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public))
                {
                    var attribs = prop.GetCustomAttributes(typeof (IgnorePropertyAttribute), true);
                    var attr = attribs.FirstOrDefault() as IgnorePropertyAttribute;
                    if (attr == null || (attr != null && !attr.Value))
                    {
                        paramNames.Add(prop.Name);
                    }
                }
                paramNameCache[o.GetType()] = paramNames;
            }
            return paramNames;
        }
    }
}
